#include <SQLServer.h>
#include <SQLServerErrorCode.h>

SQLServer::SQLServer(const SQLServerConfig &config) {

    MySQL_Driver* driver = sql::mysql::get_mysql_driver_instance();
    driver->threadInit();
    this->conn = (Connection) (driver->connect(
            config.host,
            config.username,
            config.password
    ));
    if (conn->isValid()) {
        Logi("数据库链接成功");
        this->conn->setAutoCommit(true);
        this->stm = (Statement) this->conn->createStatement();

        try {
            conn->setSchema(config.database);
        } catch (const SQLException &e) {
            this->log_sql_error(e);
            string _tp = "数据库 `" + config.database + "` 无法使用，请参考教程自行创建";
            this->error_exit(_tp);
        }
        init();
    }
    else {
        this->error_exit("数据库链接失败!!!");
    }
    driver->threadEnd();
}

bool SQLServer::init() {
    ResultSet _res(stm->executeQuery("show tables"));

    set<string> _tables;
    for (auto &i: this->tables)
        _tables.emplace(i.name);
    while (_res->next()) {
        auto _tp = _res->getString(1);
        _tables.erase(_tp);
    }
    if (!_tables.empty()) {
        //TODO 增量 新建表
        for_each(this->tables.begin(),
                 this->tables.end(),
                 [&](const TableCreateSql &_) {
                     string _log = _.name;
                     try {
                         for (auto &_i: _.query)
                             this->stm->execute(_i);
                     } catch (const SQLException &e) {
                         Logw("创建表`" + _log + "`失败");
                         this->error_exit("初始化数据库失败");
                         this->log_sql_error(e);
                     }
                     Logi("创建表`" + _log + "`成功");
                 });
    }
    else {
        Logi("数据库已初始化");
    }
    return false;
}

void SQLServer::error_exit(const string &msg) {
    this->close();
    exit(EXIT_FAILURE);
}

void SQLServer::close() {
    try {
        if (this->stm != nullptr)
            this->stm->close();
        if (this->conn != nullptr and !this->conn->isClosed())
            this->conn->close();
    } catch (exception e) {
        cout<<e.what();
    }
}

SQLServer::~SQLServer() {
    this->close();
}

void SQLServer::log_sql_error(const SQLException &e) {
    Logw(e.what());
    Logw("SQLState: " + e.getSQLState());
    Logw("ErrorCode:" + to_string(e.getErrorCode()));
}

const ErrorCode &
SQLServer::sent(const string &title, const string &content, const string &token, SQLServer::Template tmp) {
    const char *fmt = "insert into sent(message_id, token, title, content, template)\n"
                      "VALUES (UUID(),'%s','%s','%s','%s');";
    string &&_token = this->escape_sql_str(token);
    if (this->find_token(token)) {
        string &&_title = this->escape_sql_str(title);
        string &&_content = this->escape_sql_str(content);
        string &&_tmp = Template2String(tmp);

        string query;
        query.resize(strlen(fmt) + _token.size() + _title.size() + _content.size() + _tmp.size() - 4 * 2);
        sprintf((char *) query.c_str(),
                fmt,
                _token.c_str(),
                _title.c_str(),
                _content.c_str(),
                _tmp.c_str());
        try {
            bool res = this->stm->execute(query);
            return SQLServerErrorCode::normal;
        } catch (const SQLException &e) {
            return SQLServerErrorCode::sqlserver_error;
        }
    }
    else
        return SQLServerErrorCode::token_notfound;
}

bool SQLServer::find(const pair<string, string> &field_value, const string &table_name) {
    const char *fmt = "select count(*) from user where token='%s';";
    string &&value = this->escape_sql_str(field_value.second);//查找的值
    string query;
    query.resize(strlen(fmt) + value.size() - 2);
    sprintf((char *) query.c_str(), fmt, value.c_str());
    ResultSet res(this->stm->executeQuery(query));
    uint _count{};
    if (res->next())
        _count = res->getUInt(1);
    return _count != 0;
}

bool SQLServer::find_token(const string &token) {
    return this->find({"token", token}, "user");
}

string SQLServer::escape_sql_str(const string &str) {
    set<char> charset{'\\', '\''};
    string ret;
    ret.resize(str.size() * 2);
    char *_ret = (char *) ret.c_str();
    size_t escape_char_count = 0;
    for (size_t i = 0, _ret_idx = -1; i < str.size(); ++i) {
        if (charset.find(str[i]) != charset.end())
            _ret[++_ret_idx] = '\\', escape_char_count++;
        _ret[++_ret_idx] = str[i];
    }
    ret.resize(str.size() + escape_char_count);
    return ret;
}

pair<string, bool> SQLServer::sent2history(const string &token) {
    const char *fmt = "insert into history(message_id, token, title, content, template, time)\n"
                      "select *\n"
                      "from sent\n"
                      "where token = '";
    const char *fmt2 = "';";
    try {
        this->stm->execute(fmt + token + fmt2);
    } catch (const SQLException &e) {
        return {"转移失败", false};
    }
    return {"转移成功", true};
}

string Template2String(SQLServer::Template temp) {
    switch (temp) {
        case SQLServer::Template::txt:return "txt";
        case SQLServer::Template::html:return "html";
        case SQLServer::Template::json:return "json";
        case SQLServer::Template::markdown:return "markdown";
        default:return "错误";
    }
}

SQLServer::Template String2Template(const string &s) {
    if (s == "txt")
        return SQLServer::Template::txt;
    else if (s == "json")
        return SQLServer::Template::json;
    else if (s == "markdown")
        return SQLServer::Template::markdown;
    else if (s == "html")
        return SQLServer::Template::html;
    else
        return SQLServer::Template::NotFound;
}

